package com.google.code.multitester.annonations.processors;

import java.lang.reflect.Field;
import java.lang.reflect.Modifier;

/**
 * Placeholder for accessing fields. This class helps keep field and it's
 * instance together. Also there are some verifications and exceptions handling.
 * <p>
 * Static fields cannot be accessed.
 *
 * @author Yuriy Sechko
 */
public class FieldValuePointer
{
	private final Object instance;
	private final Field field;

	/**
	 * Creates placeholder and connects field and enclosing instance.
	 * Placeholder cannot be created in some circumstances.
	 *
	 * @param instance
	 *            instance that contains field
	 * @param field
	 *            field that will be accessed
	 * 
	 * @throws java.lang.IllegalArgumentException
	 *             when:
	 *             <ul>
	 *             <li>given instance or field is null</li>
	 *             <li>given field is static</li>
	 *             <li>given field doesn't belong to the specified instance
	 *             class or to the one of the superclasses in hierarchy</li>
	 *             </ul>
	 */
	public FieldValuePointer(final Object instance, final Field field)
	{
		Checkings.instanceIsNull(instance);
		Checkings.fieldIsNull(field);
		Checkings.fieldIsStatic(field);
		Checkings.areFieldAndInstanceTupeInTheSameHierarchy(instance, field);
		this.instance = instance;
		this.field = field;
	}

	/**
	 * Returns value of the field casted to the specified type
	 * 
	 * @param fieldType
	 *            expected type of the field. Actual field's value will be cast
	 *            to this type.
	 * @throws java.lang.IllegalArgumentException
	 *             when given value type is null
	 * @throws java.lang.IllegalAccessException
	 *             when field cannot be accessed
	 * @throws java.lang.ClassCastException
	 *             when actual field's value cannot be cast to specified type
	 */
	public <T> T value(final Class<T> fieldType)
	{
		if (fieldType == null)
			throw new IllegalArgumentException(
					"Type specifier should not be null.");
		try
		{
			field.setAccessible(true);
			final Object value = field.get(instance);
			field.setAccessible(false);
			return fieldType.cast(value);
		}
		catch (IllegalAccessException accessException)
		{
			final String errorMessage = String.format(
					"Can not get value of field %s on %s instance", field,
					instance);
			throw new IllegalStateException(errorMessage, accessException);
		}
	}

	/**
	 * Nothing special. Equality is calculated by equality of the
	 * {@link java.lang.Object} and equality of the
	 * {@link java.lang.reflect.Field}.
	 */
	@Override
	public boolean equals(Object o)
	{
		if (this == o)
			return true;
		if (o == null || getClass() != o.getClass())
			return false;

		FieldValuePointer pointer = (FieldValuePointer) o;

		if (field != null ? !field.equals(pointer.field)
				: pointer.field != null)
			return false;
		if (instance != null ? !instance.equals(pointer.instance)
				: pointer.instance != null)
			return false;

		return true;
	}

	/**
	 * Nothing special. Code for calculating hash is provided by IDE.
	 */
	@Override
	public int hashCode()
	{
		int result = instance != null ? instance.hashCode() : 0;
		result = 31 * result + (field != null ? field.hashCode() : 0);
		return result;
	}

	private static class Checkings
	{
		public static void areFieldAndInstanceTupeInTheSameHierarchy(
				final Object instance, final Field field)
		{
			if (!field.getDeclaringClass()
					.isAssignableFrom(instance.getClass()))
				throw new IllegalArgumentException(
						"Field's class should be assignable from instance class.");
		}

		public static void fieldIsStatic(final Field field)
		{
			if ((field.getModifiers() & Modifier.STATIC) != 0)
				throw new IllegalArgumentException(
						"Field should not be static.");
		}

		public static void fieldIsNull(final Field field)
		{
			if (field == null)
				throw new IllegalArgumentException(
						"Field placeholder should not be null.");
		}

		public static void instanceIsNull(final Object instance)
		{
			if (instance == null)
				throw new IllegalArgumentException(
						"Instance of class that contains field should not be null.");
		}
	}
}
